package homework

import (
	"database/sql/driver"
	"errors"
	"reflect"
	"strings"
)

var errInvalidEntity = errors.New("invalid entity")

// InsertStmt 作业里面我们这个只是生成 SQL，所以在处理 sql.NullString 之类的接口
// 只需要判断有没有实现 driver.Valuer 就可以了
func InsertStmt(entity interface{}) (string, []interface{}, error) {
	// 检测 entity 是否符合我们的要求
	// 我们只支持有限的几种输入

	if entity == nil {
		return "", nil, errInvalidEntity
	}

	val := reflect.ValueOf(entity)
	typ := val.Type()

	// 空结构体
	if typ.Kind() == reflect.Struct && typ.NumField() == 0 {
		return "", nil, errInvalidEntity
	}

	// 指针则转换
	if typ.Kind() == reflect.Ptr {
		typ = typ.Elem()
		val = val.Elem()
	}

	// 最多支持一级指针
	if typ.Kind() == reflect.Ptr {
		return "", nil, errInvalidEntity
	}

	// 判断是否结构体
	if typ.Kind() != reflect.Struct {
		return "", nil, errInvalidEntity
	}

	// 使用 strings.Builder 来拼接 字符串
	bd := strings.Builder{}
	valBd := strings.Builder{}

	tableName := typ.Name()

	var fds []interface{}
	var fdVals []interface{}
	fdMap := make(map[string]struct{})

	fds, fdVals = scanFields(typ, val, fds, fdVals, fdMap)

	bd.WriteString("INSERT INTO ")

	// write table name
	bd.WriteString("`")
	bd.WriteString(tableName)
	bd.WriteString("`")

	// write cols
	bd.WriteString("(")
	for i := 0; i < len(fds); i++ {
		fd := fds[i]
		bd.WriteString("`")
		bd.WriteString(fd.(string))
		bd.WriteString("`")
		valBd.WriteString("?")

		if len(fds)-i >= 2 {
			bd.WriteString(",")
			valBd.WriteString(",")
		}
	}
	bd.WriteString(")")
	bd.WriteString(" ")

	// write values
	bd.WriteString("VALUES(")
	bd.WriteString(valBd.String())
	bd.WriteString(");")
	return bd.String(), fdVals, nil
}

func scanFields(typ reflect.Type, val reflect.Value,
	fds []interface{}, fdVals []interface{}, fdMap map[string]struct{}) ([]interface{}, []interface{}) {
	num := typ.NumField()
	// 检查 fds 是否已经插入过字段
	for i := 0; i < num; i++ {
		fd := typ.Field(i)
		fdVal := val.Field(i)

		valuerType := reflect.TypeOf((*driver.Valuer)(nil)).Elem()
		isImplValuer := fd.Type.Implements(valuerType)
		// 递归条件：结构体 && 没有实现 driver.Valuer && 匿名字段
		if fd.Type.Kind() == reflect.Struct && !isImplValuer && fd.Anonymous {
			fds, fdVals = scanFields(fd.Type, fdVal, fds, fdVals, fdMap)
		}

		if fd.Type.Kind() != reflect.Struct || isImplValuer || !fd.Anonymous {
			if _, ok := fdMap[fd.Name]; !ok {
				fdMap[fd.Name] = struct{}{}
				fds = append(fds, fd.Name)
				fdVals = append(fdVals, fdVal.Interface())
			}
		}
	}
	return fds, fdVals
}
